package wxapi

import (
	"context"
	"crypto/hmac"
	"crypto/sha256"
	"crypto/tls"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/cloudwego/hertz-examples/bizdemo/hertz_gorm/biz/config"
	"github.com/cloudwego/hertz-examples/bizdemo/hertz_gorm/biz/consts"
	"github.com/cloudwego/hertz-examples/bizdemo/hertz_gorm/biz/utils"
	"github.com/cloudwego/hertz/pkg/app/client"
	"github.com/cloudwego/hertz/pkg/common/hlog"
	"github.com/cloudwego/hertz/pkg/protocol"
	consts2 "github.com/cloudwego/hertz/pkg/protocol/consts"
	"github.com/patrickmn/go-cache"
	"time"
)

var c = cache.New(120*time.Minute, 1200*time.Minute)

type WxApiProxy struct {
	client *client.Client
	appId  string
	secret string
}

func NewWxApiProxy() *WxApiProxy {
	cli, _ := client.NewClient(
		client.WithTLSConfig(&tls.Config{
			InsecureSkipVerify: true,
		}))
	return &WxApiProxy{
		client: cli,
		appId:  config.GetGlobalConfig().GetExtendFields("wx_appid"),
		secret: config.GetGlobalConfig().GetExtendFields("wx_secret"),
	}
}

func (p *WxApiProxy) JsCode2session(ctx context.Context, req Jscode2sessionReq) (*Jscode2sessionResp, error) {
	getUrl := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
		p.appId,
		p.secret,
		req.Code)
	respData, err := p.Get(ctx, getUrl)
	hlog.CtxErrorf(ctx, "JsCode2session response: %s error:%s", respData, err)
	if err != nil {
		return nil, err
	}
	rsp := &Jscode2sessionResp{}
	if err := json.Unmarshal(respData, &rsp); err != nil {
		return nil, err
	}
	return rsp, nil
}

func (p *WxApiProxy) CheckSession(ctx context.Context, req *CheckSessionReq, maxTry int) (*BaseResp, error) {
	token, err := p.getAccessToken(ctx)
	if err != nil {
		return nil, err
	}
	url := fmt.Sprintf("https://api.weixin.qq.com/wxa/checksession?access_token=%v&openid=%v&signature=%v&sig_method=hmac_sha256",
		token,
		req.OpenId,
		computeHMACSHA256Hex(req.SessionKey, ""),
	)
	hlog.CtxInfof(ctx, "CheckSession url:%s", url)
	rspBytes, err := p.Get(ctx, url)
	if err != nil {
		return nil, err
	}
	rsp := &BaseResp{}
	if err := json.Unmarshal(rspBytes, &rsp); err != nil {
		hlog.CtxErrorf(ctx, "UserLogin unmarshal response: %s error:%s", rspBytes, err)
		return nil, err
	}
	hlog.CtxInfof(ctx, "CheckSession response: %v", utils.QuickJSONMarshal(rsp))
	if rsp.ErrCode == consts.WxAccessTokenExpired && maxTry > 0 {
		_, err := p.flashAccessToken(ctx)
		if err != nil {
			return nil, err
		}
		return p.CheckSession(ctx, req, maxTry-1)
	}
	return rsp, nil
}

func (p *WxApiProxy) Get(ctx context.Context, url string) ([]byte, error) {
	reqSign := &protocol.Request{}
	resSign := &protocol.Response{}
	reqSign.SetMethod(consts2.MethodGet)
	reqSign.Header.SetContentTypeBytes([]byte("application/json"))
	reqSign.SetRequestURI(url)
	if err := p.client.Do(ctx, reqSign, resSign); err != nil {
		return nil, err
	}
	return resSign.Body(), nil
}

func (p *WxApiProxy) getAccessToken(ctx context.Context) (string, error) {
	accessToken, ok := c.Get("acccessToken")
	if !ok {
		return p.flashAccessToken(ctx)
	}
	return accessToken.(string), nil
}

func (p *WxApiProxy) flashAccessToken(ctx context.Context) (string, error) {
	gerUrl := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s",
		p.appId,
		p.secret,
	)
	reqSign := &protocol.Request{}
	resSign := &protocol.Response{}
	reqSign.SetMethod(consts2.MethodGet)
	reqSign.Header.SetContentTypeBytes([]byte("application/json"))
	reqSign.SetRequestURI(gerUrl)
	if err := p.client.Do(ctx, reqSign, resSign); err != nil {
		hlog.CtxErrorf(ctx, "WxApiProxy flashAccessToken error:%v", err.Error())
		return "", err
	}
	type asscssTokenResp struct {
		AccessToken string `json:"access_token"`
		ExpiresIn   int    `json:"expires_in"`
		ErrCode     int    `json:"errcode"`
		ErrMsg      string `json:"errmsg"`
	}
	rsp := &asscssTokenResp{}
	if err := json.Unmarshal(resSign.Body(), &rsp); err != nil {
		hlog.CtxErrorf(ctx, "UserLogin unmarshal response: %s error:%s", resSign.Body(), err.Error())
		return "", err
	}
	if rsp.ErrCode != 0 {
		return "", errors.New(rsp.ErrMsg)
	}
	c.Set("acccessToken", rsp.AccessToken, time.Duration(rsp.ExpiresIn-10)*time.Second)
	return rsp.AccessToken, nil
}

// computeHMACSHA256Hex 计算HMAC-SHA256签名并以十六进制字符串形式返回
func computeHMACSHA256Hex(data string, key string) string {
	// 将字符串转换为字节切片
	dataBytes := []byte(data)
	keyBytes := []byte(key)
	// 计算HMAC签名
	signature := computeHMACSHA256(dataBytes, keyBytes)
	// 将签名转换为十六进制字符串 hex.EncodeToString(signature)
	return hex.EncodeToString(signature)
}

// computeHMACSHA256 计算给定数据和密钥的HMAC-SHA256签名
func computeHMACSHA256(data []byte, key []byte) []byte {
	// 创建一个新的HMAC实例，使用SHA-256作为哈希函数
	h := hmac.New(sha256.New, key)
	// 写入数据
	h.Write(data)
	// 计算并返回HMAC签名
	return h.Sum(nil)
}
